前幾天學習關於 Web worker 的知識時,我發現不同地方都會使用 postMessage 函式把訊息發送出去,但不同地方使用 postMessage 的方式都有點不同,所以今天這篇文章希望統整這些 postMessage 的使用方式與差異
目前 MDN 上列出的大致上有六種
window.postMessage 通常使用在需要與 iframe 溝通的場景
當主頁面中有 iframe 存在,可以使用 iframe.contentWindow 取得 iframe 中的 window
const iframe = document.querySelector("iframe");
iframe.onload = () => {
  // 取得 iframe 裡的 window 屬性 
  const iframeWindow = iframe.contentWindow;
};
取得 iframeWindow 後,就可以直接利用這個屬性去操作 iframe 裡的一些東西,例如改變整個 iframe 網頁的背景顏色:
iframeWindow.document.querySelector("body").style.backgroundColor = "blue";
但有個問題是,以上的操作都限定在主頁面與 iframe 網頁是同源網站,如果主頁面跟 iframe 網頁不同源的話,瀏覽器會限制只能拿到某些特定的 window 的屬性 (ex. window.opener、window.location ),被限制可以拿到的屬性請看 跨域腳本API訪問
所以當不同的 window, iframe 間不同源的狀況下,無法使用上面的方式拿到另一個頁面的資料,此時需要使用 window.postMessage
window.postMessage(message, targetOrigin, [transfer])
structuredClone 算法複製後再傳送"*" 代表傳遞訊息到任意網域,但避免將洩漏敏感訊息到惡意網站,最好都提供固定的網域地址Transferable objects 轉移物件所有權到另一個目標視窗接著在另一邊的網頁使用 addEventListener('message') 接收 window.postMessage 傳來的訊息,為了避免從任意網站接收到惡意訊息,執行任何操作前都應該先檢查 訊息的發送網域(event.origin) 是否為可信任的
window.addEventListener('message', (event) => {
  // 確認訊息來源為可信任網域,執行接下來的操作
  if (event.origin === 'http://trust-website.org') {
    // 從 e.data 拿出訊息 
    console.log(event.data);
  }
})
以上述更改 iframe 網頁的背景顏色為例,改用 window.postMessage 的寫法如下:
// 主視窗 (http://main.example.com)
const iframe = document.querySelector("iframe");
iframe.onload = () => {
  // 取得 iframe 裡的 window 屬性 
  const iframeWindow = iframe.contentWindow;
  iframeWindow.postMessage({ backgroundColor: 'blue' }, );
};
// iframe 視窗 (http://iframe.example.com)
window.addEventListener('message', (e) => {
  if (event.origin === 'http://main.example.com') {
    const { backgroundColor } = e.data;
    document.querySelector("body").style.backgroundColor = backgroundColor;
  }
});
Web worker 中的 postMessage 在之前的文章中已經多次出現,可以前往 第三天的文章 - Dedicated worker 基本用法,查看更多細節的介紹
Service Worker 是瀏覽器進行網路請求時的中介層,在向 server 送出請求獲取 html, image, js 等資源的時候,可以中途攔截請求,直接回傳之前已經快取過的檔案,達到離線狀態也能正常瀏覽網頁的功能
ServiceWorker.postMessage 可以從主視窗向 service worker 發送訊息:
// 主視窗
// 註冊 service worker
navigator.serviceWorker.register("service-worker.js");
navigator.serviceWorker.ready.then((registration) => {
  // 發送訊息到 service-worker.js 
  registration.active.postMessage(
    "Test message sent immediately after creation",
  );
});
在 service-worker.js 檔案裡,獲得從主視窗傳來的訊息:
// service-worker.js
addEventListener("message", (event) => {
  console.log(`Message received: ${event.data}`);
});
在 Service worker 中,client 是指通過 Service worker 註冊過的頁面
Client.postMessage 基本上就是 ServiceWorker.postMessage 反向傳遞訊息的用法,Client.postMessage 負責從 service worker 檔案中,傳遞訊息到主視窗
以下範例傳遞訊息到所有 註冊過 service-worker.js 的頁面,首先呼叫 Clients.matchAll() 取得所有註冊過 Service worker 的頁面,接著再使用 Client.postMessage 傳遞訊息
// service-worker.js
self.clients.matchAll().then(function(clients) {
  clients.forEach(function(client) {
    client.postMessage('傳遞到主視窗的訊息');
  });
});
請參考 第九天的文章 - Transferable objects - MessagePort (Channel messaging)
請參考 第十一天的文章 - BroadcastChannel
不同的場景分別有不同的 postMessage 使用方式,共同點是每種 postMessage 都使用 structuredClone 複製數據後再傳遞訊息,而 是否可以傳遞 Transferable objects 及 是否有同源限制 則有所差異
| 種類 | structuredClone | transfer 參數 | 限制只能同源使用 | 
|---|---|---|---|
| window.postMessage | O | O | X | 
| Worker.postMessage | O | O | O | 
| ServiceWorker.postMessage | O | O | O | 
| Client.postMessage | O | O | O | 
| MessagePort.postMessage | O | O | X | 
| BroadcastChannel.postMessage | O | X | O | 
運用 postMessage 解決 iframe 與父層溝通的問題
postMessage:主頁、iframe 頁可互相傳值
postMessage可太有用了
如何与 Service Worker 通信